package jnpf.engine.custom.behavior.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.base.UserInfo;
import jnpf.constant.MsgCode;
import jnpf.database.model.entity.DbLinkEntity;
import jnpf.engine.assembly.CallActivityExPropertyDTO;
import jnpf.engine.assembly.GlobalExPropertyDTO;
import jnpf.engine.custom.constants.BasicActivitiConstants;
import jnpf.engine.custom.variables.entity.FormDataInfo;
import jnpf.engine.entity.*;
import jnpf.engine.enums.FlowNodeEnum;
import jnpf.engine.enums.FlowRecordEnum;
import jnpf.engine.enums.FlowTaskStatusEnum;
import jnpf.engine.model.flowbefore.FlowTemplateAllModel;
import jnpf.engine.model.flowengine.FlowDataModel;
import jnpf.engine.model.flowengine.FlowModel;
import jnpf.engine.model.flowengine.shuntjson.childnode.ChildNode;
import jnpf.engine.model.flowengine.shuntjson.childnode.FlowAssignModel;
import jnpf.engine.model.flowengine.shuntjson.childnode.Properties;
import jnpf.engine.model.flowengine.shuntjson.childnode.RuleListModel;
import jnpf.engine.model.flowengine.shuntjson.nodejson.ChildNodeList;
import jnpf.engine.model.flowengine.shuntjson.nodejson.Custom;
import jnpf.engine.service.FlowTemplateJsonService;
import jnpf.engine.service.FlowTemplateService;
import jnpf.engine.util.*;
import jnpf.exception.WorkFlowException;
import jnpf.model.flow.DataModel;
import jnpf.model.flow.FlowFormDataModel;
import jnpf.permission.entity.UserEntity;
import jnpf.permission.service.UserService;
import jnpf.util.*;
import jnpf.util.context.SpringContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.CallActivity;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.IOParameter;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
import org.flowable.common.engine.api.scope.ScopeTypes;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.event.impl.FlowableEventBuilder;
import org.flowable.engine.impl.bpmn.behavior.CallActivityBehavior;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityManager;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.EntityLinkUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.interceptor.StartSubProcessInstanceAfterContext;
import org.flowable.engine.interceptor.StartSubProcessInstanceBeforeContext;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;

import java.util.*;
import java.util.stream.Collectors;

import static jnpf.engine.util.BpmnModelUtils.*;
import static jnpf.engine.custom.constants.GlobalProcessVariableConstants.*;
import static jnpf.engine.util.EvalUtils.formData;

/**
 * @author LoveMyOrange
 * @create 2022-02-02 12:08
 */
@Slf4j
@SuppressWarnings("all")
public class CustomCallActivityActivityBehavior extends CallActivityBehavior {


    public CustomCallActivityActivityBehavior(CallActivity callActivity) {
        super(callActivity);
    }

    @Override
    public void execute(DelegateExecution execution) {
        ExecutionEntity executionEntity=(ExecutionEntity)execution;
        UserProvider userProvider = SpringContextHolder.getBean(UserProvider.class);
        UserService userService = SpringContextHolder.getBean(UserService.class);
        FlowTemplateJsonService flowTemplateJsonService = SpringContextHolder.getBean(FlowTemplateJsonService.class);
        ServiceAllUtil serviceAllUtil = SpringContextHolder.getBean(ServiceAllUtil.class);
        FlowTaskUtil flowTaskUtil = SpringContextHolder.getBean(FlowTaskUtil.class);
        FlowTemplateService flowTemplateService = SpringContextHolder.getBean(FlowTemplateService.class);
        RuntimeService runtimeService = SpringContextHolder.getBean(RuntimeService.class);
        String currentActivityId = execution.getCurrentActivityId();

        CallActivity callActivity = (CallActivity) execution.getCurrentFlowElement();
        DelegateExecution parent = execution.getParent();
        Map<String, Object> processVariables = parent.getVariables();


        Process process = getBpmnModelMainProcess(execution.getProcessDefinitionId());
        String taskProperty = getAttributeValueByTaskDefinitionKey(process,callActivity.getId() , "callActivity");
        jnpf.engine.model.flowengine.shuntjson.childnode.Properties callActivityProperties = JSONObject.parseObject(taskProperty.toString(), new TypeReference<jnpf.engine.model.flowengine.shuntjson.childnode.Properties>() {});
        String subProcessBusinessKey=RandomUtil.uuId();
        String calledElement = callActivity.getCalledElement();
        Map<String, FlowTemplateAllModel> templateJsonMap = FlowContextHolder.getTemplateJson();
        ProcessDefinition processDefinition = SpringContextHolder.getBean(RepositoryService.class).createProcessDefinitionQuery()
                .processDefinitionKey(calledElement).latestVersion().singleResult();
        FlowTemplateAllModel flowTemplateAllModel = templateJsonMap.get(processDefinition.getId());
        if(flowTemplateAllModel==null){
            LambdaQueryWrapper<FlowTemplateJsonEntity> lambdaQueryWrapper= new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(FlowTemplateJsonEntity::getProcessDefinitionId,processDefinition.getId());
            FlowTemplateJsonEntity flowTemplateJsonEntity = flowTemplateJsonService.getOne(lambdaQueryWrapper);
            FlowTemplateEntity flowTemplateEntity = flowTemplateService.getById(flowTemplateJsonEntity.getTemplateId());
            flowTemplateAllModel = new FlowTemplateAllModel();
            flowTemplateAllModel.setTemplate(flowTemplateEntity);
            flowTemplateAllModel.setTemplateJson(flowTemplateJsonEntity);
            FlowContextHolder.addTemplateJson(processDefinition.getId(),flowTemplateAllModel);
        }
        FlowTemplateEntity template = flowTemplateAllModel.getTemplate();
        FlowTemplateJsonEntity templateJson = flowTemplateAllModel.getTemplateJson();
        String flowTemplateJson = templateJson.getFlowTemplateJson();
        ChildNode childNode = JsonUtil.getJsonToBean(flowTemplateJson, ChildNode.class);
        Properties subProperties = childNode.getProperties();
        UserInfo userInfo = userProvider.get();
        String formId = childNode.getProperties().getFormId();
        Map<String, Object> infoData = flowTaskUtil.infoData(formId, execution.getProcessInstanceBusinessKey());
        infoData.put("@flowFullName", templateJson.getTemplateName());
        infoData.put("@flowFullCode", templateJson.getSecondKey());
        infoData.put("@launchUserName", userInfo.getUserName());
        infoData.put("@launchTime", DateUtil.daFormat(new Date()));
        String assignee = executionEntity.getVariable("assignee", String.class);
        UserEntity applyUserInfo = serviceAllUtil.getUserInfo(assignee);
        String title = applyUserInfo.getRealName() + "发起的" + templateJson.getTemplateName() + "(子流程)";
        if (!FlowNature.TitleType.equals(subProperties.getTitleType())) {
            title = FlowJsonUtil.field(subProperties.getTitleContent(), infoData, "1");
        }


        //获取数据传递
        List<FlowAssignModel> assignList = callActivityProperties.getAssignList();
        Map<String, Object> data = new HashMap<>();

        if(CollUtil.isNotEmpty(assignList)){
            Map<String,Object> assignMap = new HashMap<>();
            Map<String, Object> map = formData(assignMap, assignList, execution.getProcessInstanceId());
            data.putAll(assignMap);

            Map<String,Map<String, Object>> dbMap = new HashMap<>();
            //进行新增 或更新
            dbMap.put(subProcessBusinessKey + "_jnpf_" +formId ,data);
            for (String idAll : dbMap.keySet()) {
                String[] idList = idAll.split("_jnpf_");
                Map<String, Object> formData = dbMap.get(idAll);
                FlowFormDataModel model = FlowFormDataModel.builder().formId(idList[1]).id(idList[0]).map(formData).formOperates(callActivityProperties.getFormOperates()).build();
                FlowContextHolder.addData(idList[0],formData);
                serviceAllUtil.createOrUpdate(model);
            }
        }



        handleCallActivityInfo(execution,subProcessBusinessKey,title);




        ExecutionEntity subProcessInstance = executionEntity.getSubProcessInstance();

        String subProcessDefinitionId = subProcessInstance.getProcessDefinitionId();
        Process subProcess = getBpmnModelMainProcess(subProcessDefinitionId);
        String property = getAttributeValueByTaskDefinitionKey(subProcess, "", "global");
        GlobalExPropertyDTO exPropertyDTO = JSONObject.parseObject(property, new TypeReference<GlobalExPropertyDTO>() {
        });
        //


        Map<String,Object> variablesMap=new HashMap<>();
        variablesMap.put(BUSINESS_TITLE, title);
        variablesMap.put(PROCESS_CATEGORY, template.getCategory());
//        if (StringUtils.isNotBlank(flowModel.getBillNo())) {
//            variablesMap.put(BILL_CODE, flowModel.getBillNo());
//        }
        variablesMap.put(FLOW_URGENT, 1);

        variablesMap.put(APPLY_USER_ID, executionEntity.getVariable("assignee",String.class)/*userInfo.getUserId()*/);//实际审批人
        variablesMap.put(CURRENT_CREATE_TIME, new Date());
        variablesMap.put(BUSINESS_KEY, subProcessBusinessKey);
        variablesMap.put(FLOW_ENGINE_ID,templateJson.getTemplateId());
        variablesMap.put(FLOW_TEMPLATE_JSON_ID,templateJson.getId());
        variablesMap.put(BATCH_FLAG,templateJson.getBatch());
        variablesMap.put(CALL_ACTIVITY_FLAG,true);//
        Boolean autoSubmit = callActivityProperties.getAutoSubmit();
        variablesMap.put(CALL_ACTIVITY_AUTO_COMPLETE_FIRST_NODE,autoSubmit);
        if(autoSubmit){
            variablesMap.put(PROCESS_STATUS, FlowTaskStatusEnum.Handle.getCode());
        }
        else{
            variablesMap.put(PROCESS_STATUS, FlowTaskStatusEnum.Draft.getCode());
        }
        runtimeService.setVariables(subProcessInstance.getProcessInstanceId(),variablesMap);
    }

    private void handleCallActivityInfo(DelegateExecution execution,String subProcessBusinessKey,String businessTitle) {

        ExecutionEntity executionEntity = (ExecutionEntity) execution;
        CallActivity callActivity = (CallActivity) executionEntity.getCurrentFlowElement();
        CommandContext commandContext = CommandContextUtil.getCommandContext();
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
        ProcessDefinition processDefinition = getProcessDefinition(execution, callActivity, processEngineConfiguration);
        Process subProcess = ProcessDefinitionUtil.getProcess(processDefinition.getId());
        if (subProcess == null) {
            throw new FlowableException("Cannot start a sub process instance. Process model " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") could not be found");
        }
        FlowElement initialFlowElement = subProcess.getInitialFlowElement();
        if (initialFlowElement == null) {
            throw new FlowableException("No start element found for process definition " + processDefinition.getId());
        }
        if (ProcessDefinitionUtil.isProcessDefinitionSuspended(processDefinition.getId())) {
            throw new FlowableException("Cannot start process instance. Process definition " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") is suspended");
        }
        ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager(commandContext);
        ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
        String businessKey = null;
        businessKey=subProcessBusinessKey;

        Map<String, Object> variables = new HashMap<>();
        StartSubProcessInstanceBeforeContext instanceBeforeContext = new StartSubProcessInstanceBeforeContext(businessKey, null,
                callActivity.getProcessInstanceName(), new HashMap<>(), new HashMap<>(), executionEntity, callActivity.getInParameters(),
                callActivity.isInheritVariables(), initialFlowElement.getId(), initialFlowElement, subProcess, processDefinition);


        if (processEngineConfiguration.getStartProcessInstanceInterceptor() != null) {
            processEngineConfiguration.getStartProcessInstanceInterceptor().beforeStartSubProcessInstance(instanceBeforeContext);
        }
        ExecutionEntity subProcessInstance = processEngineConfiguration.getExecutionEntityManager().createSubprocessInstance(
                instanceBeforeContext.getProcessDefinition(), instanceBeforeContext.getCallActivityExecution(),
                instanceBeforeContext.getBusinessKey(), instanceBeforeContext.getInitialActivityId());
        FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
        if (eventDispatcher != null && eventDispatcher.isEnabled()) {
            processEngineConfiguration.getEventDispatcher().dispatchEvent(
                    FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.PROCESS_CREATED, subProcessInstance),
                    processEngineConfiguration.getEngineCfgKey());
        }

        subProcessInstance.setVariables(processDataObjects(subProcess.getDataObjects()));
        if (instanceBeforeContext.isInheritVariables()) {

            Map<String, Object> executionVariables = execution.getVariables();
            Map<String, Object> transientVariables = execution.getTransientVariables();
            for (Map.Entry<String, Object> entry : executionVariables.entrySet()) {

                // The executionVariables contain all variables, including the transient variables.
                // Hence why that map is iterated and the transient variables are split off
                String variableName = entry.getKey();
                if (transientVariables.containsKey(variableName)) {
                    instanceBeforeContext.getTransientVariables().put(variableName, entry.getValue());

                } else {
                    instanceBeforeContext.getVariables().put(variableName, entry.getValue());

                }
            }

        }


        for (IOParameter inParameter : instanceBeforeContext.getInParameters()) {

            Object value = null;
            if (StringUtils.isNotEmpty(inParameter.getSourceExpression())) {
                Expression expression = expressionManager.createExpression(inParameter.getSourceExpression().trim());
                value = expression.getValue(execution);

            } else {
                value = execution.getVariable(inParameter.getSource());
            }

            String variableName = null;
            if (StringUtils.isNotEmpty(inParameter.getTargetExpression())) {
                Expression expression = expressionManager.createExpression(inParameter.getTargetExpression());
                Object variableNameValue = expression.getValue(execution);
                if (variableNameValue != null) {
                    variableName = variableNameValue.toString();
                } else {
//                    LOGGER.debug("In parameter target expression {} did not resolve to a variable name, this is most likely a programmatic error",
//                            inParameter.getTargetExpression());
                }

            } else if (StringUtils.isNotEmpty(inParameter.getTarget())){
                variableName = inParameter.getTarget();

            }

            instanceBeforeContext.getVariables().put(variableName, value);
        }

        if (!instanceBeforeContext.getVariables().isEmpty()) {
            initializeVariables(subProcessInstance, instanceBeforeContext.getVariables());
        }

        if (!instanceBeforeContext.getTransientVariables().isEmpty()) {
            initializeTransientVariables(subProcessInstance, instanceBeforeContext.getTransientVariables());
        }

        String processInstanceName = null;
        processInstanceName=businessTitle;
        subProcessInstance.setName(processInstanceName);
        if (eventDispatcher != null && eventDispatcher.isEnabled()) {
            eventDispatcher.dispatchEvent(FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.ENTITY_INITIALIZED, subProcessInstance),
                    processEngineConfiguration.getEngineCfgKey());
        }
        if (processEngineConfiguration.isEnableEntityLinks()) {
            EntityLinkUtil.createEntityLinks(execution.getProcessInstanceId(), executionEntity.getId(), callActivity.getId(),
                    subProcessInstance.getId(), ScopeTypes.BPMN);
        }
        if (StringUtils.isNotEmpty(callActivity.getProcessInstanceIdVariableName())) {
            Expression expression = expressionManager.createExpression(callActivity.getProcessInstanceIdVariableName());
            String idVariableName = (String) expression.getValue(execution);
            if (StringUtils.isNotEmpty(idVariableName)) {
                execution.setVariable(idVariableName, subProcessInstance.getId());
            }
        }

        ExecutionEntity subProcessInitialExecution = executionEntityManager.createChildExecution(subProcessInstance);
        subProcessInitialExecution.setCurrentFlowElement(instanceBeforeContext.getInitialFlowElement());

        if (processEngineConfiguration.getStartProcessInstanceInterceptor() != null) {
            StartSubProcessInstanceAfterContext instanceAfterContext = new StartSubProcessInstanceAfterContext(subProcessInstance, subProcessInitialExecution,
                    instanceBeforeContext.getVariables(), instanceBeforeContext.getTransientVariables(), instanceBeforeContext.getCallActivityExecution(),
                    instanceBeforeContext.getInParameters(), instanceBeforeContext.getInitialFlowElement(), instanceBeforeContext.getProcess(),
                    instanceBeforeContext.getProcessDefinition());
            processEngineConfiguration.getStartProcessInstanceInterceptor().afterStartSubProcessInstance(instanceAfterContext);
        }

        processEngineConfiguration.getActivityInstanceEntityManager().recordSubProcessInstanceStart(executionEntity, subProcessInstance);

        CommandContextUtil.getAgenda().planContinueProcessOperation(subProcessInitialExecution);

        if (eventDispatcher != null && eventDispatcher.isEnabled()) {
            Map<String, Object> allVariables = new HashMap<>();
            allVariables.putAll(instanceBeforeContext.getVariables());
            allVariables.putAll(instanceBeforeContext.getTransientVariables());
            eventDispatcher.dispatchEvent(FlowableEventBuilder.createProcessStartedEvent(subProcessInitialExecution, allVariables, false),
                    processEngineConfiguration.getEngineCfgKey());
        }

    }






}
